package com.wh.user.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.wh.basic.dto.LoginDto;
import com.wh.basic.handle.BaseContext;
import com.wh.basic.jwt.JwtTokenUtils;
import com.wh.basic.jwt.LoginData;
import com.wh.basic.jwt.RsaUtils;
import com.wh.basic.utils.WxConstants;
import com.wh.basic.vo.AjaxResult;
import com.wh.basic.vo.tokenVersion;
import com.wh.org.exception.BusinessException;
import com.wh.system.dao.MenuDao;
import com.wh.system.dao.PermissionDao;
import com.wh.system.pojo.Menu;
import com.wh.user.dao.IUserDao;
import com.wh.user.dao.LoginInfoDao;
import com.wh.user.dao.WxuserDao;
import com.wh.user.pojo.LoginInfo;
import com.wh.user.pojo.User;
import com.wh.user.pojo.Wxuser;
import com.wh.user.service.ILoginInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class LoginInfoServiceimpl implements ILoginInfoService {

    @Autowired
    private LoginInfoDao loginInfoDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxuserDao wxuserDao;
    @Autowired
    private IUserDao userDao;
    @Autowired
    private PermissionDao permissionMapper;
    @Autowired
    private MenuDao menuMapper;


    @Override
    public void add(LoginInfo loginInfo) {
        loginInfoDao.insert(loginInfo);
    }

    @Override
    public void del(Long id) {
        loginInfoDao.deleteById(id);
    }

    @Override
    public void upd(Long id) {
        LoginInfo loginInfo = new LoginInfo();
        loginInfoDao.updateById(loginInfo);
    }

    @Override
    public List<LoginInfo> findAll() {
        return loginInfoDao.selectList(null);
    }

    @Override
    public LoginInfo findById(Long id) {
        return loginInfoDao.selectById(id);
    }

    @Override
    public IPage<LoginInfo> findByPage(LoginInfo loginInfo) {
        return null;
    }

    @Override
    public void patchDel(Long[] ids) {
        loginInfoDao.deleteBatchIds(Arrays.asList(ids));
    }

    @Transactional
    @Override
    public Map<String, Object> accountLogin(LoginDto loginDto) {
        String username = loginDto.getUsername();
        String password = loginDto.getPassword();
        //1-校验--判断登录信息是否为空
        if (StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {
            throw new BusinessException("信息不能为空");
        }

        //2-校验-账号校验（支持手机号，邮箱，用户名多种登录方式）
        QueryWrapper<LoginInfo> qw = new QueryWrapper<>();
        qw.eq("phone", username).or().eq("account", username).or().eq("email", username);
        LoginInfo one = loginInfoDao.selectOne(qw);
        if (one == null) {
            throw new BusinessException("账号或密码错误");
        }

        //3-判断 账号属性 （普通用户或者管理员）
        //这是传来的登录账号    与数据库查处的账号对比
        if (loginDto.getType() != one.getType()) {
            throw new BusinessException("您登陆的平台有误,请前往正确的平台登录!");
        }

        //4-校验密码  加盐加密
        String md5Pwd = SecureUtil.md5(password + one.getSalt());
        if (!md5Pwd.equals(one.getPassword())) {
            throw new BusinessException("账号或密码错误!!!");
        }
        //5-校验是否有效  通过以上判断，与数据库查询出来的一致
        if (!one.getDisable()) {
            throw new BusinessException("该账号已被禁用，请联系管理员");
        }
        //6-生成token 并保存到redis里设置时效
//        String token = IdUtil.fastSimpleUUID();
//        redisTemplate.opsForValue().set(token,one,31, TimeUnit.MINUTES);
        /**
         * 不用无状态token 生成jwt
         */

        //7 封装返回值
        Map<String, Object> map = loginSuccessJwtHandler(one);
        //登录成功  将用户id存入baseContext全局对象中
        BaseContext.setCurrentId(one.getId());
        return map;
    }

    /**
     * 微信登录
     *
     * @param code 微信授权码
     * @return
     */
    @Override
    public AjaxResult weChatLogin(String code) {
        //1根据code发送第二个请求，httputil，返回json字符串
        String url = WxConstants.GET_ACK_URL.replace("APPID", WxConstants.APPID)
                .replace("SECRET", WxConstants.SECRET)
                .replace("CODE", code);
        //1.2 调用工具类发送请求，返回json格式的字符串
        String jsonStr = com.baidu.ai.aip.utils.HttpUtil.httpGet(url);
        /*
         * {
         *   "access_token":"ACCESS_TOKEN",
         *   "expires_in":7200,
         *   "refresh_token":"REFRESH_TOKEN",
         *   "openid":"OPENID",
         *   "scope":"SCOPE",
         *   "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
         * }
         */

        //使用fastJson处理，获得token，openId
        JSONObject jsonObject = JSON.parseObject(jsonStr);
        String accessToken = jsonObject.getString("access_token");    //授权令牌
        String openid = jsonObject.getString("openid");

        //根据openid查询user表
        QueryWrapper<Wxuser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("openid", openid);
        Wxuser wxUser = wxuserDao.selectOne(queryWrapper);

        //如果微信用户查到了，== 一起拿扫过码
        if (wxUser != null) {
            //获取微信所关联的用户信息
            User user = userDao.selectById(wxUser.getUserId());

            //如果已经关联了user对象，扫码免密登录
            if (user != null) {
                //查询登录了信息,并返回给前台,让前台可以进行数据回显
                LoginInfo loginInfo = loginInfoDao.selectById(user.getLogininfoId());
                String token = IdUtil.fastSimpleUUID(); //36位随机字符串
                redisTemplate.opsForValue().set(token, loginInfo, 30, TimeUnit.MINUTES);
                //封住返回值：Map【token，logininfo】
                HashMap<String, Object> map = new HashMap<>();
                map.put("token", token);
                loginInfo.setSalt(null);
                loginInfo.setPassword(null);
                map.put("logininfo", loginInfo);

                return AjaxResult.me().setResultObj(map);
            } else {
                throw new BusinessException("该用户不存在，请重新注册");
            }

        }
        //   4.2 如果没有查到(不论是有没有微信用户,还是有没有普通用户) = 响应给前端token，openid，前端跳转到绑定页面
        return AjaxResult.me().setSuccess(false).setResultObj("accessToken=" + accessToken + "&openid=" + openid);
    }


    private Map<String, Object> loginSuccessJwtHandler(LoginInfo logininfo) {
        //处理掉密码和盐值 => 安全
        logininfo.setPassword(null);
        logininfo.setSalt(null);
        //jwt token => 私钥 + LoginData【logininfo + permissions + menus】
        //本来就只需要传递token到前端就可以了，因为token中数据全都有，只需要解密就可以了
        //前端ks代码没有写解密的代码。所以除了将token传递到前端，还需要传递
        //1.logininfo-显示用户信息 2.menus-动态路由-动态菜单 3.permissions-资源权限/按钮权限 4.token-后续发请求要判断是否登录了
        //前台用户没有权限和菜单，前台用户的Map中只有：登录信息logininfo，token
        Map<String, Object> map = new HashMap<>();
        LoginData loginData = new LoginData();
        map.put("logininfo", logininfo);
        loginData.setLogininfo(logininfo);

        if (logininfo.getType() == 0) { //0-表示管理员
            //一：想办法获取当前登录人的权限：permissions - 查那些字段
            List<String> permissions = permissionMapper.getPermissionsByLogininfoId(logininfo.getId());

            map.put("permissions", permissions);
            loginData.setPermissions(permissions);
            //二：想办法获取当前登录人的菜单：menus - 查那些字段
            List<Menu> menus = menuMapper.getMenusByLogininfoId(logininfo.getId());
            System.out.println(menus);
            map.put("menus", menus);
            loginData.setMenus(menus);
            //三：将permissions，menus，logininfo封装到LoginData中
        }

        try {
            //四：通过工具类获取私钥
            PrivateKey privateKey;
            try (InputStream privateKeyStream = JwtTokenUtils.class.getClassLoader().getResourceAsStream("auth_rsa_pri.txt")) {
                if (privateKeyStream == null) {
                    throw new RuntimeException("找不到密钥文件：auth_rsa_pri.txt");
                }
                privateKey = RsaUtils.getPrivateKey(privateKeyStream);
            } catch (IOException e) {
                throw new RuntimeException("读取密钥失败：auth_rsa_pri.txt", e);
            }
          //  PrivateKey privateKey = RsaUtils.getPrivateKey(JwtTokenUtils.class.getClassLoader().getResource("auth_rsa_pri.txt").getFile());

            //五：使用工具类和私钥将LoginData加密 => token[jwt token]
            //过期时间一天 86400秒
            //封装的时候封装一个version
            //token的版本号
            String version= tokenVersion.TokenVersion;
            String jwtToken = JwtTokenUtils.generateTokenExpire(loginData, privateKey, 86400,version);
            //六：将permissions，menus，logininfo，token[jwt token]装到Map中响应给前端
            map.put("token", jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }


}
